package org.ocean.general.sample.helper;

import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.UpdateProvider;
import org.apache.ibatis.builder.annotation.ProviderSqlSource;
import org.apache.ibatis.mapping.MappedStatement;
import org.ocean.general.sample.exception.BatisException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;

public class MapperHelper{

    private static Logger logger = LoggerFactory.getLogger(MapperHelper.class);

    private static final String INIT_COMMON_MAPPER_PATH="org.ocean.mybatis.boot.mapper.mapper.BaseMapper";
    private final Map<Class<?>,MapperTemplate> registerMapper = new ConcurrentHashMap<>();

    /**
     * 注册的接口
     */
    private List<Class<?>> registerClass = new ArrayList<Class<?>>();


    public MapperHelper() {
        if(this.registerMapper.isEmpty()){
            this.registerMapper(INIT_COMMON_MAPPER_PATH,false);
        }
    }

    public void processMappedStatement(MappedStatement mappedStatement){
        MapperTemplate mapperTemplate = isMapperMethod(mappedStatement.getId());
        if(mappedStatement.getSqlSource() instanceof ProviderSqlSource){
            setSqlSource(mappedStatement,mapperTemplate);
        }
    }
    public void registerMapper(String mapperClass,boolean isInit){
        try {
            Class<?> baseMapperCalss = Class.forName(mapperClass);
            this.registerMapper(baseMapperCalss,isInit);
        } catch (ClassNotFoundException e) {
            logger.error("未找到通用mapper类...",e.getStackTrace());
        }
    }

    public void registerMapper(Class<?> mapperClass,boolean isInit){
        if(!this.registerMapper.containsKey(mapperClass) && isInit){
            this.registerClass.add(mapperClass);
            this.registerMapper.put(mapperClass,fromMapperClass(mapperClass));
        }
        Class<?>[] classes = mapperClass.getInterfaces();
        if(Objects.nonNull(classes) && classes.length>0){
            for (Class<?> aClass : classes) {
                registerMapper(aClass,true);
            }
        }
    }

    private MapperTemplate fromMapperClass(Class<?> baseMapperClass){
        Method[] methods =  baseMapperClass.getDeclaredMethods();
        Set<String> methodMap = new HashSet<>();
        AtomicReference<Class<?>> classAtomicReference = new AtomicReference<>();
        Arrays.stream(methods).forEach(method -> {
            if(method.isAnnotationPresent(SelectProvider.class)){
                SelectProvider selectProvider =method.getAnnotation(SelectProvider.class);
                classAtomicReference.set(selectProvider.type());
            }else if(method.isAnnotationPresent(InsertProvider.class)){
                InsertProvider insertProvider =method.getAnnotation(InsertProvider.class);
                classAtomicReference.set(insertProvider.type());
            }else if(method.isAnnotationPresent(UpdateProvider.class)){
                UpdateProvider updateProvider =method.getAnnotation(UpdateProvider.class);
                classAtomicReference.set(updateProvider.type());
            }else if(method.isAnnotationPresent(DeleteProvider.class)){
                DeleteProvider deleteProvider =method.getAnnotation(DeleteProvider.class);
                classAtomicReference.set(deleteProvider.type());
            }

            methodMap.add(method.getName());
        });
        if(Objects.isNull(classAtomicReference.get()) || !MapperTemplate.class.isAssignableFrom(classAtomicReference.get())){

        }
        MapperTemplate mapperTemplate = null;
        try{
            mapperTemplate = (MapperTemplate) classAtomicReference.get().getConstructor(Class.class,MapperHelper.class).newInstance(baseMapperClass,this);
        }catch (BatisException e){
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        for (String methodName:methodMap){
            try {
                mapperTemplate.addMethod(methodName,classAtomicReference.get().getMethod(methodName,MappedStatement.class));
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
        return mapperTemplate;
    }
    public MapperTemplate isMapperMethod(String msId){
        for (Map.Entry<Class<?>,MapperTemplate> entry: registerMapper.entrySet()){
            if(entry.getValue().supportMethod(msId)){
                return entry.getValue();
            }
        }
        return null;
    }

    public void setSqlSource(MappedStatement mappedStatement, MapperTemplate mapperTemplate){
        if(Objects.nonNull(mapperTemplate)){
            mapperTemplate.setSqlSource(mappedStatement);
        }
    }
}
